package com.ruicar.afs.cloud.product.util;

import com.ruicar.afs.cloud.product.enums.Operator;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

/**
 * @author xudongdong
 * @date 2020-05-21
 */
@UtilityClass
@Slf4j
public class Vector {
    /**
     * 利用java.util.Vector计算四则运算字符串表达式的值，如果抛出异常，则说明表达式有误，这里就没有控制
     *
     * @param computeExpr 四则运算字符串表达式
     * @return 计算结果
     */
    public double computeWithVector(String computeExpr) {
        StringTokenizer tokenizer = new StringTokenizer(computeExpr, "+-*/()", true);
        java.util.Vector<Double> nums = new java.util.Vector<>();
        java.util.Vector<Operator> operators = new java.util.Vector<>();
        Map<String, Operator> computeOder = getComputeOper();
        Operator curOder;
        String currentEle;
        while (tokenizer.hasMoreTokens()) {
            currentEle = tokenizer.nextToken().trim();
            //只处理非空字符
            if (!"".equals(currentEle)) {
                // 数字
                if (isNum(currentEle)) {
                    nums.add(Double.valueOf(currentEle));
                    // 非数字，即括号或者操作符
                } else {
                    curOder = computeOder.get(currentEle);
                    // 是运算符
                    if (curOder != null) {
                        // 运算列表不为空且之前的运算符优先级较高则先计算之前的优先级
                        while (!operators.isEmpty()
                                && operators.lastElement().priority() >= curOder
                                .priority()) {
                            compute(nums, operators);
                        }
                        // 把当前运算符放在运算符队列的末端
                        operators.add(curOder);
                    } else { // 括号
                        // 左括号时直接放入操作列表中
                        if ("(".equals(currentEle)) {
                            operators.add(Operator.BRACKETS);
                            // 当是右括号的时候就把括号里面的内容执行了。
                        } else {
                            // 循环执行括号里面的内容直到遇到左括号为止。试想这种情况(2+5*2)
                            while (!operators.lastElement().equals(Operator.BRACKETS)) {
                                compute(nums, operators);
                            }
                            //移除左括号
                            operators.remove(operators.size() - 1);
                        }
                    }
                }
            }
        }
        // 经过上面代码的遍历后最后的应该是nums里面剩两个数或三个数，operators里面剩一个或两个运算操作符
        while (!operators.isEmpty()) {
            compute(nums, operators);
        }
        return nums.firstElement();
    }

    /**
     * 判断一个字符串是否是数字类型
     *
     * @param str
     * @return
     */
    private boolean isNum(String str) {
        //数字的正则表达式
        String numRegex = "^\\d+(\\.\\d+)?$";
        return Pattern.matches(numRegex, str);
    }

    /**
     * 获取运算操作符
     *
     * @return
     */
    private Map<String, Operator> getComputeOper() {
        return new HashMap<String, Operator>() {
            // 运算符
            private static final long serialVersionUID = 7706718608122369958L;

            {
                put("+", Operator.PLUS);
                put("-", Operator.MINUS);
                put("*", Operator.MULTIPLY);
                put("/", Operator.DIVIDE);
            }
        };
    }

    /**
     * 取nums的最后两个数字，operators的最后一个运算符进行运算，然后把运算结果再放到nums列表的末端
     *
     * @param nums
     * @param operators
     */
    private void compute(java.util.Vector<Double> nums, java.util.Vector<Operator> operators) {
        // 第二个数字，当前队列的最后一个数字
        Double num2 = nums.remove(nums.size() - 1);
        // 第一个数字，当前队列的最后一个数字
        Double num1 = nums.remove(nums.size() - 1);
        // 取最后一个运算符进行计算
        Double computeResult = operators.remove(operators.size() - 1).compute(
                num1, num2);
        // 把计算结果重新放到队列的末端
        nums.add(computeResult);
    }
}
